// Copyright 2008 and onwards Matt Burkhart, Mike Chan.
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.

#include <assert.h>
#include <fuse.h>
#include <stdlib.h>
#include <fstream>

#include "logger.h"
#include "switchfs.h"

DiskLogger g_logger("/tmp/switchfs.log");
SwitchFS g_switchfs(&g_logger);

#define LOG(format, args...) g_logger.Log(format, ## args)

namespace switchfs {
int statfs(const char* path, struct statvfs* buffer) {
  return g_switchfs.statfs(path, buffer);
}

int access(const char* path, int mode) {
  return g_switchfs.access(path, mode);
}

int getattr(const char* path, struct stat* attributes) {
  return g_switchfs.getattr(path, attributes);
}

int chmod(const char* path, mode_t mode) {
  return g_switchfs.chmod(path, mode);
}

int chown(const char* path, uid_t uid, gid_t gid) {
  return g_switchfs.chown(path, uid, gid);
}

int utime(const char* path, struct utimbuf* buffer) {
  return g_switchfs.utime(path, buffer);
}

int mkdir(const char* path, mode_t mode) {
  return g_switchfs.mkdir(path, mode);
}

int readdir(const char* path, void* buffer, fuse_fill_dir_t filler,
	    off_t offset, struct fuse_file_info* fi) {
  return g_switchfs.readdir(path, buffer, filler, offset, fi);
}

int mknod(const char* path, mode_t mode, dev_t dev) {
  return g_switchfs.mknod(path, mode, dev);
}

int unlink(const char* path) {
  return g_switchfs.unlink(path);
}

int rmdir(const char* path) {
  return g_switchfs.rmdir(path);
}

int rename(const char* old_path, const char* new_path) {
  return g_switchfs.rename(old_path, new_path);
}

int open(const char* path, struct fuse_file_info *fi) {
  return g_switchfs.open(path, fi);
}

int release(const char* path, struct fuse_file_info* fi) {
  return g_switchfs.release(path, fi);
}

int read(const char* path, char* buffer, size_t size, off_t offset,
	 struct fuse_file_info* fi) {
  return g_switchfs.read(path, buffer, size, offset, fi);
}

int write(const char* path, const char* buffer, size_t size, off_t offset,
	  struct fuse_file_info* fi) {
  return g_switchfs.write(path, buffer, size, offset, fi);
}

int fsync(const char* path, int datasync, struct fuse_file_info* fi) {
  return g_switchfs.fsync(path, datasync, fi);
}

int truncate(const char* path, off_t size) {
  return g_switchfs.truncate(path, size);
}
}  // namespace switchfs

int main(int argc, char* argv[]) {
  // Parse the command line arguments using the FUSE-provided mechanism. This
  // also permits the use of SwitchFS within fstab.
  struct SwitchFSOptions {
    char* config_path;
    int check;
    int balance;
  } switchfs_options = {NULL};
  fuse_opt switchfs_command_line[] = {
    {"config=%s", offsetof(SwitchFSOptions, config_path), 0},
    {"-check", offsetof(SwitchFSOptions, check), 1},
    {"-balance", offsetof(SwitchFSOptions, balance), 1}, {NULL}
  };
  fuse_args args = FUSE_ARGS_INIT(argc, argv);
  assert(!fuse_opt_parse(&args, &switchfs_options, switchfs_command_line, NULL));
  assert(switchfs_options.config_path != NULL);

  // Parse the configuration file in order to initialize the SwitchFS instance.
  // The configuration file is a line and/or space delimited set of source
  // directories.
  LOG("Mounting SwitchFS. config_file: %s", switchfs_options.config_path);
  {
    std::ifstream config_file(switchfs_options.config_path);
    assert(config_file.is_open());
    std::string source_path;
    while (config_file >> source_path) {
      g_switchfs.AddSource(source_path);
      LOG("Source added: %s", source_path.c_str());
    }
  }

  if (switchfs_options.check || switchfs_options.balance) {
    LOG("Checking filesystem directory structure...");
    int error = g_switchfs.Check("/", switchfs_options.balance);
    if (error) {
      LOG("Check failed with error, %d.", error);
      exit(error);
    }
    LOG("Check complete.");
  }

  // Start up the FUSE integration layer.
  struct fuse_operations fuse_method_map = {NULL};
  fuse_method_map.statfs = switchfs::statfs;
  fuse_method_map.access = switchfs::access;
  fuse_method_map.getattr = switchfs::getattr;
  fuse_method_map.chmod = switchfs::chmod;
  fuse_method_map.chown = switchfs::chown;
  fuse_method_map.utime = switchfs::utime;
  fuse_method_map.mkdir = switchfs::mkdir;
  fuse_method_map.readdir = switchfs::readdir;
  fuse_method_map.mknod = switchfs::mknod;
  fuse_method_map.unlink = switchfs::unlink;
  fuse_method_map.rmdir = switchfs::rmdir;
  fuse_method_map.rename = switchfs::rename;
  fuse_method_map.open = switchfs::open;
  fuse_method_map.release = switchfs::release;
  fuse_method_map.read = switchfs::read;
  fuse_method_map.write = switchfs::write;
  fuse_method_map.fsync = switchfs::fsync;
  fuse_method_map.truncate = switchfs::truncate;

  return fuse_main(args.argc, args.argv, &fuse_method_map);
}
